/**
 * 
 */
package com.gragra.distances;
import it.unimi.dsi.fastutil.ints.Int2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap.Entry;
import com.gragra.sampling.vector.VectorStructure;
/**
 * This implementation of the mean interface is based on hte cosine similarity measure, it is not based on a proper
 * distance, but that actually makes it more useful, since vectors that are very different but point in similar
 * directions are judged as similar, all values will be between 2 (most dissimilar) and 0 (most similar)
 * @author Christoph Teichmann
 * created May 2, 2013 11:10:06 AM
 * @version 0.1
 */
public class CosineSimilarity implements Mean
{
	/**
	 * used to keep track of whether a VectorStructure has already been added
	 */
	private boolean empty = true;
	/**
	 * this map holds the values that have already been added and once the set method has been called it will hold
	 * the normed vector
	 */
	private final Int2ObjectMap<Int2DoubleOpenHashMap> sums = new Int2ObjectOpenHashMap<Int2DoubleOpenHashMap>();
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#add(com.gragra.sampling.vector.VectorStructure)
	 */
	@Override
	public void add(VectorStructure entry)
	{
		this.empty = false;
		double norm = entry.getL2Norm();
		int[][] codes = entry.getCodes();
		double[] values = entry.getValues();
		for(int i=0;i<codes.length;++i)
		{
			int[] code = codes[i];
			double val = values[i];
			add(sums,code[0],code[1],val/norm);
		}
	}
	/**
	 * this method is used to add data to the sums that are used internally
	 * @param sums2
	 * @param i
	 * @param j
	 * @param d
	 */
	private void add(Int2ObjectMap<Int2DoubleOpenHashMap> main, int i, int j,
			double val)
	{
		Int2DoubleOpenHashMap map = main.get(i);
		if(map == null)
		{
			map = new Int2DoubleOpenHashMap();
			main.put(i, map);
		}
		map.addTo(j, val);
	}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#getDistance(com.gragra.sampling.vector.VectorStructure)
	 */
	@Override
	public double getDistance(VectorStructure vs)
	{
		double[] vals = vs.getValues();
		double norm = vs.getL2Norm();
		int[][] codes = vs.getCodes();
		double ret = 0.0;
		for(int i=0;i<vals.length;++i)
		{
			int[] code = codes[i];
			ret += this.get(this.sums,code[0],code[1])*(vals[i]/norm);
		}
		return 1-ret;
	}
	/**
	 * returns a value currently stored in the sum map
	 * @param sums2
	 * @param i
	 * @param j
	 * @return
	 */
	private double get(Int2ObjectMap<Int2DoubleOpenHashMap> main, int i, int j)
	{
		Int2DoubleOpenHashMap map = main.get(i);
		if(map == null)
		{return 0.0;}
		return map.get(j);
	}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#empty()
	 */
	@Override
	public boolean empty()
	{return this.empty;}
	/* (non-Javadoc)
	 * @see com.gragra.distances.Mean#set()
	 */
	@Override
	public void set()
	{
		double norm = DistanceTools.getL2Norm(this.sums);
		for(Entry<Int2DoubleOpenHashMap> ent : this.sums.int2ObjectEntrySet())
		{
			Int2DoubleOpenHashMap map = ent.getValue();
			IntIterator iit = map.keySet().iterator();
			while(iit.hasNext())
			{
				int number = iit.nextInt();
				double val = map.get(number);
				map.put(number, val/norm);
			}
		}
	}
}